﻿using System;
using System.ServiceModel.Dispatcher;
using System.Web;
using System.Web.Caching;

namespace PlainConcepts.WCF.Extensions.Cache
{
	/// <summary>
	/// Operation invoker that adds caching capabilities
	/// </summary>
	public class CachedInvoker : IOperationInvoker
	{
		private IOperationInvoker _innerInvoker;
		private int _cacheDuration = 0;
		private string _operationName;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="innerInvoker">Original operation invoker.</param>
		/// <param name="cacheDuration">The number of seconds the response should be held in the cache.</param>
		/// <param name="operationName">Name of the operation to wich the cache operation invoker will be apply.</param>
		public CachedInvoker(IOperationInvoker innerInvoker, int cacheDuration, string operationName)
		{
			_innerInvoker = innerInvoker;
			_cacheDuration = cacheDuration;
			_operationName = operationName;
		}

		#region IOperationInvoker Members

		/// <summary>
		/// Implementation for IsSynchronous
		/// </summary>
		/// <remarks>
		/// <see cref="System.ServiceModel.Dispatcher.IOperationInvoker.IsSynchronous"/>
		/// </remarks>
		public bool IsSynchronous
		{
			get { return _innerInvoker.IsSynchronous; }
		}

		/// <summary>
		/// Implementatión for AllocateInputs
		/// </summary>
		/// <returns>An array of object</returns>
		/// <remarks>
		/// <see cref="System.ServiceModel.Dispatcher.IOperationInvoker.AllocateInputs"/>
		/// </remarks>
		public object[] AllocateInputs()
		{
			return _innerInvoker.AllocateInputs();
		}

		/// <summary>
		/// Implementation for Invoke
		/// </summary>
		/// <param name="instance">Instance</param>
		/// <param name="inputs">Inputs</param>
		/// <param name="outputs">Outputs</param>
		/// <returns>An array of object</returns>
		/// <remarks>
		/// <see cref="System.ServiceModel.Dispatcher.IOperationInvoker.Invoke"/>
		/// </remarks>
		public object Invoke(object instance, object[] inputs, out object[] outputs)
		{
			string key = GetCacheKey(_operationName, inputs);
			object result = HttpRuntime.Cache[key];
			if (result == null)
			{
				result = _innerInvoker.Invoke(instance, inputs, out outputs);
				HttpRuntime.Cache.Add(
					key, result, null, DateTime.Now.AddSeconds(_cacheDuration), 
					System.Web.Caching.Cache.NoSlidingExpiration,
					CacheItemPriority.Default, null);
			}
			outputs = new object[0];
			return result;
		}


		/// <summary>
		/// Implementation for InvokeBegin
		/// </summary>
		/// <param name="instance">Instance</param>
		/// <param name="inputs">Inputs</param>
		/// <param name="callback">Callback</param>
		/// <param name="state">State</param>
		/// <returns>IAsynResult</returns>
		/// <remarks>
		/// <see cref="System.ServiceModel.Dispatcher.IOperationInvoker.InvokeBegin"/>
		/// </remarks>
		public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
		{
			return _innerInvoker.InvokeBegin(instance, inputs, callback, state);
		}

		/// <summary>
		/// Implementation for InvokeBegin
		/// </summary>
		/// <param name="instance">Instance</param>
		/// <param name="outputs">Outputs</param>
		/// <param name="result">Result</param>
		/// <returns>Object</returns>
		/// <remarks>
		/// <see cref="System.ServiceModel.Dispatcher.IOperationInvoker.InvokeEnd"/>
		/// </remarks>
		public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
		{
			return _innerInvoker.InvokeEnd(instance, out outputs, result);
		}

		/// <summary>
		/// Get a key for caching based on operation inputs and name.
		/// </summary>
		/// <param name="inputs">Inputs</param>
		/// <param name="operationName">Operation name</param>
		/// <returns>The key.</returns>
		private static string GetCacheKey(string operationName, object[] inputs)
		{
			string key = operationName;
			for (int i = 0; i < inputs.Length; i++)
			{
				key = key + inputs[i].ToString();
			}
			return key;
		}

		#endregion
	}
}

